/*
 * Copyright (C) 2021 Pascal Nowack
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "config.h"

#include "grd-rdp-surface.h"

#include "grd-hwaccel-nvidia.h"
#include "grd-rdp-damage-detector-cuda.h"
#include "grd-rdp-damage-detector-memcmp.h"
#include "grd-session-rdp.h"

static void
destroy_hwaccel_util_objects (GrdRdpSurface *rdp_surface)
{
  if (rdp_surface->cuda_stream)
    {
      grd_hwaccel_nvidia_destroy_cuda_stream (rdp_surface->hwaccel_nvidia,
                                              rdp_surface->cuda_stream);
      rdp_surface->cuda_stream = NULL;
    }
  if (rdp_surface->avc.main_view)
    {
      grd_hwaccel_nvidia_clear_mem_ptr (rdp_surface->hwaccel_nvidia,
                                        &rdp_surface->avc.main_view);
    }
}

static gboolean
maybe_render_pending_frame (gpointer user_data)
{
  GrdRdpSurface *rdp_surface = user_data;

  grd_session_rdp_maybe_encode_pending_frame (rdp_surface->session_rdp,
                                              rdp_surface);

  return G_SOURCE_CONTINUE;
}

static gboolean
pending_render_source_dispatch (GSource     *source,
                                GSourceFunc  callback,
                                gpointer     user_data)
{
  g_source_set_ready_time (source, -1);

  return callback (user_data);
}

static GSourceFuncs pending_render_source_funcs =
{
  .dispatch = pending_render_source_dispatch,
};

GrdRdpSurface *
grd_rdp_surface_new (GrdSessionRdp    *session_rdp,
                     GrdHwAccelNvidia *hwaccel_nvidia,
                     GMainContext     *render_context,
                     uint32_t          refresh_rate)
{
  g_autofree GrdRdpSurface *rdp_surface = NULL;
  GSource *pending_render_source;

  rdp_surface = g_malloc0 (sizeof (GrdRdpSurface));
  rdp_surface->session_rdp = session_rdp;
  rdp_surface->hwaccel_nvidia = hwaccel_nvidia;
  rdp_surface->refresh_rate = refresh_rate;

  if (hwaccel_nvidia &&
      !grd_hwaccel_nvidia_create_cuda_stream (hwaccel_nvidia,
                                              &rdp_surface->cuda_stream))
    return NULL;

  if (hwaccel_nvidia)
    {
      GrdRdpDamageDetectorCuda *detector;

      detector = grd_rdp_damage_detector_cuda_new (hwaccel_nvidia,
                                                   rdp_surface->cuda_stream);
      if (!detector)
        {
          destroy_hwaccel_util_objects (rdp_surface);
          return NULL;
        }

      rdp_surface->detector = GRD_RDP_DAMAGE_DETECTOR (detector);
    }
  else
    {
      GrdRdpDamageDetectorMemcmp *detector;

      detector = grd_rdp_damage_detector_memcmp_new ();
      rdp_surface->detector = GRD_RDP_DAMAGE_DETECTOR (detector);
    }

  g_mutex_init (&rdp_surface->surface_mutex);

  pending_render_source = g_source_new (&pending_render_source_funcs,
                                        sizeof (GSource));
  g_source_set_callback (pending_render_source, maybe_render_pending_frame,
                         rdp_surface, NULL);
  g_source_set_ready_time (pending_render_source, -1);
  g_source_attach (pending_render_source, render_context);
  rdp_surface->pending_render_source = pending_render_source;

  return g_steal_pointer (&rdp_surface);
}

void
grd_rdp_surface_free (GrdRdpSurface *rdp_surface)
{
  g_assert (!rdp_surface->gfx_surface);

  g_assert (!rdp_surface->pending_framebuffer);

  if (rdp_surface->pending_render_source)
    {
      g_source_destroy (rdp_surface->pending_render_source);
      g_clear_pointer (&rdp_surface->pending_render_source, g_source_unref);
    }

  g_clear_pointer (&rdp_surface->surface_mapping, g_free);

  g_mutex_clear (&rdp_surface->surface_mutex);

  g_clear_object (&rdp_surface->detector);
  destroy_hwaccel_util_objects (rdp_surface);

  g_free (rdp_surface);
}

uint32_t
grd_rdp_surface_get_width (GrdRdpSurface *rdp_surface)
{
  return rdp_surface->width;
}

uint32_t
grd_rdp_surface_get_height (GrdRdpSurface *rdp_surface)
{
  return rdp_surface->height;
}

GrdRdpSurfaceMapping *
grd_rdp_surface_get_mapping (GrdRdpSurface *rdp_surface)
{
  return rdp_surface->surface_mapping;
}

gboolean
grd_rdp_surface_is_rendering_inhibited (GrdRdpSurface *rdp_surface)
{
  return rdp_surface->rendering_inhibited;
}

void
grd_rdp_surface_set_size (GrdRdpSurface *rdp_surface,
                          uint32_t       width,
                          uint32_t       height)
{
  rdp_surface->width = width;
  rdp_surface->height = height;
}

void
grd_rdp_surface_set_mapping (GrdRdpSurface        *rdp_surface,
                             GrdRdpSurfaceMapping *surface_mapping)
{
  g_clear_pointer (&rdp_surface->surface_mapping, g_free);
  rdp_surface->surface_mapping = surface_mapping;
}

void
grd_rdp_surface_invalidate_surface (GrdRdpSurface *rdp_surface)
{
  rdp_surface->valid = FALSE;
}

void
grd_rdp_surface_inhibit_rendering (GrdRdpSurface *rdp_surface)
{
  g_mutex_lock (&rdp_surface->surface_mutex);
  rdp_surface->rendering_inhibited = TRUE;
  g_mutex_unlock (&rdp_surface->surface_mutex);
}

void
grd_rdp_surface_uninhibit_rendering (GrdRdpSurface *rdp_surface)
{
  rdp_surface->rendering_inhibited = FALSE;
}

void
grd_rdp_surface_trigger_render_source (GrdRdpSurface *rdp_surface)
{
  g_source_set_ready_time (rdp_surface->pending_render_source, 0);
}

void
grd_rdp_surface_reset (GrdRdpSurface *rdp_surface)
{
  rdp_surface->needs_no_local_data = FALSE;

  if (rdp_surface->avc.main_view)
    {
      grd_hwaccel_nvidia_clear_mem_ptr (rdp_surface->hwaccel_nvidia,
                                        &rdp_surface->avc.main_view);
    }
}
